// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//


// The architecture of gcu driver
// 		GCUClient => GCUClientProxy => GCUProxy => GCU
// GCUClientProxy and GCUProxy is designed for external clients, for example camera, to acess GCU, 
// by the utility of GCUClient, just like blit and line in display driver.
// Multiple clients can submit their command buffers into GCU and wait for the end of execution,
// and the submitions are protected by a critical section. A client's context is saved by GCU. It'll
// be recovered upon submition, if the GCU is accessed by another client before.
// Currently supported context is the source and destination data buffers.
// 
// The power management
// The GCU IST wait for the interrupt events. If a eeob interrupt is received, it'll put GCU in to 
// power down mode, and GCU idle event is set. In the submit routine, if GCU is idle, it'll be powered
// up, until next eeob interrupt. The power handling is also protected by critical section.
// The power up/down action includes, request clock for sidecar and GCU, notify IPM for supported 
// processor state. 
//
// The GCU ring buffer
// The GCU command buffer is a ring buffer. New commands are appended at the tail, and then adjust GCU
// register tail pointer. The appending may wrap to the top of the command buffer. Once the execution
// pointer of GCU arrives at the tail pointer, the execution finishes, and eeob interrupt is signaled.
// Eeob interrupt must be masked until next submition, otherwise it's always signaled because the 
// execution pointer equals the tail pointer

#include <windows.h>
#include <ceddk.h>
#include "monahans_base_regs.h"
#include "gcu.h"
#include "xllp_intc.h"


GCU::GCU(IPMClient* ipm)
{
    gcu = this;
    ipm_client = ipm;
    need_reset = 1;

    init();
}

GCU* GCU::gcu(0);

GCU* GCU::get_gcu()
{
    return gcu;
}

void dump_regs(volatile GCURegs* regs)
{
    NKDbgPrintfW(TEXT("gccr %x!\r\n"), regs->gccr);
    NKDbgPrintfW(TEXT("gciscr %x!\r\n"), regs->gciscr);
    NKDbgPrintfW(TEXT("gciecr %x!\r\n"), regs->gciecr);
    NKDbgPrintfW(TEXT("gcnopid %x!\r\n"), regs->gcnopid);
    NKDbgPrintfW(TEXT("gcalphaset %x!\r\n"), regs->gcalphaset);
    NKDbgPrintfW(TEXT("gctset %x!\r\n"), regs->gctset);
    NKDbgPrintfW(TEXT("gcflags %x!\r\n"), regs->gcflags);

    NKDbgPrintfW(TEXT("gcrbbr %x!\r\n"), regs->gcrbbr);
    NKDbgPrintfW(TEXT("gcrblr %x!\r\n"), regs->gcrblr);
    NKDbgPrintfW(TEXT("gcrbhr %x!\r\n"), regs->gcrbhr);
    NKDbgPrintfW(TEXT("gcrbtr %x!\r\n"), regs->gcrbtr);
    NKDbgPrintfW(TEXT("gcrbexhr %x!\r\n"), regs->gcrbexhr);
}

void dump_buf(UINT32* buf, UINT size)
{
    for (UINT i = 0; i < size ; i++)
        NKDbgPrintfW(TEXT("0x%08x -> 0x%08x!\r\n"), i, buf[i]);
}


bool GCU::init()
{
    idle_event = CreateEvent(NULL, TRUE, FALSE, sync_event_name);

    if (!idle_event)
    {
        RETAILMSG(1, (TEXT("2D GCU: Failed to create idle event.\r\n")));
        return 0;
    }

    if (!init_memory())
        return 0;

    init_power();

    // reset();

    intr_thread.ThreadStart();
    return 1;
}

bool GCU::init_memory()
{
    PHYSICAL_ADDRESS pa;
	
    if (!regs)
    {
        pa.QuadPart = MONAHANS_BASE_REG_PA_2D;
        regs = (volatile GCURegs*)MmMapIoSpace(pa, sizeof(*regs), FALSE);
        if (!regs)
        {
            RETAILMSG(1, (TEXT("2D GCU: MmMapIoSpace Fail")));
            return 0;
        }
    }

    DMA_ADAPTER_OBJECT adapter;

    ring_buf.size = ring_buf_size;

    ring_buf.buf = (UINT32*)HalAllocateCommonBuffer(&adapter, ring_buf.size, &pa, FALSE);
    ring_buf.base = pa.LowPart;

    if (!ring_buf.buf)
    {
        ERRORMSG(1, (TEXT("CRITICAL ERROR: 2D GCU ring buffer allocation failed")));
        return 0;
    }
    return 1;
}


void GCU::reset()
{
    lock.Lock();
    ResetEvent(idle_event);
    set_power_mode(1);
    _reset();
    lock.Unlock();
}

void GCU::_reset()
{
    RETAILMSG(1, (TEXT("gcu reset!")));

    regs->gccr = GCCR_ABORT | (regs->gccr & 0xF);
    regs->gccr = GCCR_STOP | (regs->gccr & 0xF);

    /* Setup ring buffer */
    regs->gcrblr = 0;
    regs->gcrbbr = ring_buf.base;
    regs->gcrbtr = ring_buf.base;
    regs->gcrblr = ring_buf.size;

    regs->gccabaddr = 0;
    regs->gctabaddr = 0;
    regs->gcmabaddr = 0;

    // enable interrupts, a eeob interrupt will put gcu into idle state
    regs->gciscr |= GCISCR_MASK;
    regs->gciecr &=~GCIECR_MASK;
    regs->gciecr |= GCIECR_PF_INTEN | GCIECR_IIN_INTEN 
                    | GCIECR_IOP_INTEN | GCIECR_EEOB_INTEN;
    
    // run
    regs->gccr = (regs->gccr & ~GCCR_STOP) & GCCR_MASK;
    context = 0;

    need_reset = 0;
    // dump_regs(regs);
}

void GCU::sync()
{
    WaitForSingleObject(idle_event, INFINITE);
}

void GCU::handle_intr()
{
    //NKDbgPrintfW(TEXT("handle_intr!\r\n"));
    // dump_regs(regs);

    
    UINT32 intr_status = regs->gciscr & regs->gciecr;
    regs->gciscr |= GCISCR_MASK;

    if (intr_status & GCISCR_EEOB_INTST)
    {
        lock.Lock();

        if (regs->gcrbexhr == regs->gcrbtr)
        {

            //NKDbgPrintfW(TEXT("set idle event!\r\n"));
 
            SetEvent(idle_event);

            // disable eeob interrupt
            regs->gciecr &= ~GCIECR_EEOB_INTEN;
            set_power_mode(0);
            // dump_regs(regs);

        }

        //NKDbgPrintfW(TEXT("handle intr unlock!\r\n"));

        lock.Unlock();
    }

    if (intr_status & GCISCR_PF_INST)
        RETAILMSG(1, (TEXT("gcu interrtup: illegal/incorrect pixel format")));

    if (intr_status & GCISCR_IIN_INTST)
        RETAILMSG(1, (TEXT("gcu interrtup: illegal instruction")));

    if (intr_status & GCISCR_IOP_INTST)
        RETAILMSG(1, (TEXT("gcu interrtup: illegal operation")));
}

GCUIntrThread::GCUIntrThread() : CMiniThread(0, 1),
                                 irq(XLLP_INTC_S_GRAPHICS)
{
}



DWORD GCUIntrThread::ThreadRun()
{
    if (!init())
        return 1;

    RETAILMSG(1, (TEXT("2D GCU: thread running!!!\r\n")));


    while(1)
    {
        DWORD rv = WaitForSingleObject(intr_event, INFINITE);
    
        if (rv != WAIT_OBJECT_0)
        {
            RETAILMSG(1, (TEXT("wait for gcu interrupt failed")));
            Sleep(1000);
            continue;
        }


        GCU::get_gcu()->handle_intr();

        InterruptDone(sys_intr);
    }
}
            
bool GCUIntrThread::init()
{
    DWORD rv;

    intr_event = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (!intr_event)
    {
        RETAILMSG(1, (TEXT("2D GCU: Failed to create interrupt event.\r\n")));
        return 0;
    }

    // Request the IRQ for the 2D GCU.
    rv = KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, (LPVOID)&irq, sizeof(UINT32), &sys_intr, sizeof(UINT32), NULL);

    if (rv != TRUE)
    {
        RETAILMSG(1, (TEXT("2D GCU: IRQ registration failed.\r\n")));
        return 0;
    }

    RETAILMSG(1, (TEXT("2D GCU: Got sys interrupt [%d]\r\n"), sys_intr));

    if (!InterruptInitialize(sys_intr, intr_event, NULL, 0)) 
    {
        RETAILMSG(1, (TEXT("2D GCU: Failed to initialize interrupt event.\r\n")));
        return 0;
    }

    return 1;
}

bool dump = 0;


bool GCU::submit(UINT32* buf, UINT32 size)
{
    UINT32 empty, tail_empty;

    // convert to counts of bytes
    size <<= 2;

    // make sure submit always succeed when gcu idle
    if (size >= ring_buf.size)
        return 0;

    if (size == 0)
        return 1;

    if (need_reset)
    {
        ResetEvent(idle_event);
        set_power_mode(1);
        _reset();
    }

    if (WaitForSingleObject(idle_event, 0) == WAIT_OBJECT_0)
    {
        // submit will always succeed here 
        ResetEvent(idle_event);
        set_power_mode(1);
    }

    UINT32 head = regs->gcrbexhr - ring_buf.base;
    UINT32 tail = regs->gcrbtr - ring_buf.base;


    if (tail >= head)
    {
        empty = ring_buf.size - (tail - head);
        tail_empty = ring_buf.size - tail;
    } else
        tail_empty = empty = head - tail;

    if (size < tail_empty)
    {
        memcpy((char*)ring_buf.buf + tail, buf, size);
        tail += size;
    }
    else if (size < empty)
    {
        memcpy((char*)ring_buf.buf + tail, buf, tail_empty);
        UINT32 remain = size - tail_empty;
        memcpy((char*)ring_buf.buf, (char*)buf + tail_empty, remain);
        tail = remain;
    } else
    {
        // buffer is too big
        return 0;
    }

    regs->gcrbtr = ring_buf.base + tail;

    // clear eeob interrupt status and renable it
    regs->gciscr |= GCISCR_EEOB_INTST;
    regs->gciecr |= GCIECR_EEOB_INTEN;

    return 1;
}

void GCU::invalidate_context(GCUProxy* invalid_context)      
{
    if (context == invalid_context)    
        context = 0;
}

bool GCUProxy::submit(UINT32* new_context_buf, UINT new_context_size, 
                 UINT32* cmd_buf, UINT cmd_size)
{
    GCU::get_gcu()->handle_client(this, cmd_buf, cmd_size);

    // sync context
    if (new_context_buf)
    {
        if (new_context_size > context_cmd_size)
            return 0;

        UINT i;
        for (i = 0; i < new_context_size; i++)
            context_buf[i] = new_context_buf[i];
        
        context_size = new_context_size;
    }
    return 1;
}

bool GCU::handle_client(GCUProxy* proxy, UINT32* cmd_buf, UINT32 cmd_size)
{
    bool rv;

    lock.Lock();

    if (context != proxy)
    {
        rv = submit(proxy->get_context_cmd_buf(), 
                    proxy->get_context_cmd_size());
        
        if (!rv)
        {
            lock.Unlock();
            return rv;
        }

        context = proxy;
    }

    rv = gcu->submit(cmd_buf, cmd_size);
    if (!rv)
    {
        gcu->sync();
        rv = gcu->submit(cmd_buf, cmd_size);
    }

    lock.Unlock();
    return rv;
}

void GCUProxy::sync()
{
    GCU::get_gcu()->sync();
}

GCUProxy::~GCUProxy()
{
    GCU::get_gcu()->invalidate_context(this);
}

UINT32* GCUProxy::get_context_cmd_buf()
{
    return context_buf;
}


UINT GCUProxy::get_context_cmd_size()
{
    return context_size;
}
